'use strict'; const { Document } = require('/document'); const { DocumentCommand, AddChildNodesCommandBuilder, CompoundCommandBuilder, NodeChildType } = require('/commands'); const { ShapeNodeDefinition } = require('/nodes'); const { Shape, ShapeType } = require('/shapes'); const { FillDescriptor, GradientFill } = require('/fills'); const { Gradient, RGBA8 } = require('/colours'); const { LineStyleDescriptor } = require('/linestyle'); const { Rectangle, Transform } = require('/geometry'); const { GradientFillType } = require('affinity:fills'); const { BlendMode } = require('affinity:common'); const { Dialog, DialogResult } = require('/dialog'); const { UnitType } = require('/units'); const dlg = Dialog.create('Random Linear Gradient Fill'); const col = dlg.addColumn(); const grp = col.addGroup('Gradients'); function addRow(column, label, defaultVal, min, max) { // grp.enableSeparator = false; const editor = grp.addUnitValueEditor(label, UnitType.Number, UnitType.Number, defaultVal, min, max); editor.precision = 0; return editor; } function addCheck(column, label, defaultVal) { // const grp = column.addGroup(label); grp.enableSeparator = false; return grp.addCheckBox(label, defaultVal); } const layerCountEditor = addRow(col, 'Number of Layers', 10, 1, 300); const minEditor = addRow(col, 'Minimum Stops', 5, 1, 200); const maxEditor = addRow(col, 'Maximum Stops', 10, 1, 200); const randomPosCheck = addCheck(col, 'Random Stop Positions', false); const minLocEditor = addRow(col, 'Minimum Location (%)', 0, 0, 100); const maxLocEditor = addRow(col, 'Maximum Location (%)', 100, 0, 100); //minLocEditor.setIsEnabledBy(randomPosCheck); //maxLocEditor.setIsEnabledBy(randomPosCheck); const randomMidCheck = addCheck(col, 'Random Midpoints', false); const minMidEditor = addRow(col, 'Minimum Midpoint (%)', 0, 0, 100); const maxMidEditor = addRow(col, 'Maximum Midpoint (%)', 100, 0, 100); //minMidEditor.setIsEnabledBy(randomMidCheck); //maxMidEditor.setIsEnabledBy(randomMidCheck); const randomOpacityCheck = addCheck(col, 'Random Opacity', false); const minOpacityEditor = addRow(col, 'Minimum Opacity (%)', 0, 0, 100); const maxOpacityEditor = addRow(col, 'Maximum Opacity (%)', 100, 0, 100); //minOpacityEditor.setIsEnabledBy(randomOpacityCheck); //maxOpacityEditor.setIsEnabledBy(randomOpacityCheck); const redFactor = addRow(col, 'Red Factor', 100, 0, 3000); const greenFactor = addRow(col, 'Green Factor', 100, 0, 3000); const blueFactor = addRow(col, 'Blue Factor', 100, 0, 3000); const dlgResult = dlg.runModal(); if (dlgResult.value !== DialogResult.Ok.value) { console.log('Cancelled.'); } else { const layerCount = Math.max(1, Math.min(300, Math.round(layerCountEditor.value))); let minStops = Math.max(1, Math.min(200, Math.round(minEditor.value))); let maxStops = Math.max(1, Math.min(200, Math.round(maxEditor.value))); if (minStops > maxStops) { const t = minStops; minStops = maxStops; maxStops = t; } const randomPositions = randomPosCheck.value; const randomMidpoints = randomMidCheck.value; const randomOpacity = randomOpacityCheck.value; let minLoc = Math.max(0, Math.min(100, Math.round(minLocEditor.value))) / 100; let maxLoc = Math.max(0, Math.min(100, Math.round(maxLocEditor.value))) / 100; if (minLoc > maxLoc) { const t = minLoc; minLoc = maxLoc; maxLoc = t; } let minMid = Math.max(0, Math.min(100, Math.round(minMidEditor.value))) / 100; let maxMid = Math.max(0, Math.min(100, Math.round(maxMidEditor.value))) / 100; if (minMid > maxMid) { const t = minMid; minMid = maxMid; maxMid = t; } let minOpacity = Math.max(0, Math.min(100, Math.round(minOpacityEditor.value))) / 100; let maxOpacity = Math.max(0, Math.min(100, Math.round(maxOpacityEditor.value))) / 100; if (minOpacity > maxOpacity) { const t = minOpacity; minOpacity = maxOpacity; maxOpacity = t; } const doc = Document.current; const spread = doc.currentSpread; // Set current spread AFTER dialog closes (dialog resets the spread context) doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); const extents = spread.getSpreadExtents({ includeSpread: true }); const x = extents.x, y = extents.y, w = extents.width, h = extents.height; const lineStyle = LineStyleDescriptor.createDefault(0); const noFill = FillDescriptor.createNone(); const rect = new Rectangle(x, y, w, h); const xf = Transform.createTranslate(x, y + h / 2).multiply(Transform.createScale(w, 1)); function buildStops(stopCount) { let positions; if (randomPositions) { positions = Array.from({ length: stopCount }, () => minLoc + Math.random() * (maxLoc - minLoc)); positions.sort((a, b) => a - b); positions[0] = minLoc; positions[stopCount - 1] = maxLoc; } else { positions = Array.from({ length: stopCount }, (_, i) => stopCount === 1 ? 0 : i / (stopCount - 1)); } return positions.map(pos => { const opacity = randomOpacity ? minOpacity + Math.random() * (maxOpacity - minOpacity) : 1.0; return { colour: RGBA8( Math.floor(redFactor.value * Math.random() * 2.56), Math.floor(greenFactor.value * Math.random() * 2.56), Math.floor(blueFactor.value * Math.random() * 2.56), Math.round(opacity * 255) ), position: pos, midpoint: randomMidpoints ? minMid + Math.random() * (maxMid - minMid) : 0.5 }; }); } // Build each layer as its own command, collect into a CompoundCommandBuilder, // then execute once as a single undoable action. const nodeBuilder = AddChildNodesCommandBuilder.create(); const compoundBuilder = CompoundCommandBuilder.create(); for (let i = 0; i < layerCount; i++) { const stopCount = minStops + Math.floor(Math.random() * (maxStops - minStops + 1)); const stops = buildStops(stopCount); const gradient = Gradient.create(stops); const gradientFill = GradientFill.create(gradient, GradientFillType.Linear); const fillDescriptor = FillDescriptor.create(gradientFill, false, xf, BlendMode.Normal, false); const shape = Shape.create(ShapeType.Rectangle); const def = ShapeNodeDefinition.create(shape, rect, fillDescriptor, noFill, lineStyle, noFill); def.userDescription = `Gradient Layer ${i + 1} of ${layerCount} (${stopCount} stops)`; nodeBuilder.addNode(def); compoundBuilder.addCommand(nodeBuilder.createCommandAndReset(false, NodeChildType.Main)); } doc.executeCommand(compoundBuilder.createCommand()); console.log(`Created ${layerCount} gradient layer${layerCount > 1 ? 's' : ''} as a single undoable action.`); }